'''
     Torchvision Alexnet Model
'''

import torch
import torch.nn as nn
from typing import Any, Optional
from torchvision.models._utils import _ovewrite_named_param

from torchvision import transforms
from torchvision.models._meta import _IMAGENET_CATEGORIES

from ._weights_api import Weights, WeightsEnum

__all__ = ["AlexNet", "AlexNet_Weights", "alexnet"]

class AlexNet(nn.Module):
    def __init__(self, num_classes: int = 1000, dropout: float = 0.5) -> None:
        super().__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
            nn.Conv2d(64, 192, kernel_size=5, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
            nn.Conv2d(192, 384, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(384, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
        )
        self.avgpool = nn.AdaptiveAvgPool2d((6, 6))
        self.classifier = nn.Sequential(
            nn.Dropout(p=dropout),
            nn.Linear(256 * 6 * 6, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(p=dropout),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Linear(4096, num_classes),
        )

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        x = self.features(x)
        x = self.avgpool(x)
        x = torch.flatten(x, 1)
        x = self.classifier(x)
        return x
    
class AlexNet_Weights(WeightsEnum):        
    IMAGENET1K_V1 = Weights(
        url="https://s3.us-east-1.wasabisys.com/visionlab-members/fdoshi/PROJECT_contour-integration/alexnet_bagnets/logs/set01/final_weight_dir/set01/alexnet/08e23172-f46e-437a-ba82-7575acfa50e3/alexnet_final_weights-958fca999f.pth",
        transforms=dict(
            val_transform=transforms.Compose([
                transforms.Resize(256),
                transforms.CenterCrop(224),
                transforms.ToTensor(),
                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
            ]),
            test_transform=transforms.Compose([
                transforms.Resize(224),
                transforms.CenterCrop(224),
                transforms.ToTensor(),
                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
            ])
        ),
        meta={
            "repo": "https://github.com/harvard-visionlab/alexnet_bagnets",
            "log_url": "https://s3.us-east-1.wasabisys.com/visionlab-members/fdoshi/PROJECT_contour-integration/alexnet_bagnets/logs/set01/final_weight_dir/set01/alexnet/08e23172-f46e-437a-ba82-7575acfa50e3/alexnet_log-958fca999f.txt",
            "params_url": "https://s3.us-east-1.wasabisys.com/visionlab-members/fdoshi/PROJECT_contour-integration/alexnet_bagnets/logs/set01/final_weight_dir/set01/alexnet/08e23172-f46e-437a-ba82-7575acfa50e3/alexnet_params-958fca999f.json",
            "train_script": "train_supervised.py",
            "task": "supervised1k",
            "dataset": "imagenet1k",
            "arch": "alexnet",
            "num_params": 61100840,
            "categories": _IMAGENET_CATEGORIES,
            "_metrics": {
                "ImageNet-1K-res256-tta0": {
                    "acc@1": 56.9119987487793,
                    "acc@5": 79.20999908447266,
                },
                "ImageNet-1K-res256-tta1": {
                    "acc@1": 57.69999694824219,
                    "acc@5": 79.7979965209961,
                }
            },
            "_docs": """
                Torchvision's Alexnet Architecture Trained with our Recipe.
            """,
        },
    )
    DEFAULT = IMAGENET1K_V1

def alexnet(*, weights=None, progress: bool = True, **kwargs: Any) -> AlexNet:
    """AlexNet model architecture from `One weird trick for parallelizing convolutional neural networks <https://arxiv.org/abs/1404.5997>`__.
    .. note::
        AlexNet was originally introduced in the `ImageNet Classification with
        Deep Convolutional Neural Networks
        <https://papers.nips.cc/paper/2012/hash/c399862d3b9d6b76c8436e924a68c45b-Abstract.html>`__
        paper. Our implementation is based instead on the "One weird trick"
        paper above.
    Args:
        weights (:class:`~torchvision.models.AlexNet_Weights`, optional): The
            pretrained weights to use. See
            :class:`~torchvision.models.AlexNet_Weights` below for
            more details, and possible values. By default, no pre-trained
            weights are used.
        progress (bool, optional): If True, displays a progress bar of the
            download to stderr. Default is True.
        **kwargs: parameters passed to the ``torchvision.models.squeezenet.AlexNet``
            base class. Please refer to the `source code
            <https://github.com/pytorch/vision/blob/main/torchvision/models/alexnet.py>`_
            for more details about this class.
    """
    
    weights = AlexNet_Weights.verify(weights)
    
    if weights is not None:
        _ovewrite_named_param(kwargs, "num_classes", len(weights.meta["categories"]))
        
    model = AlexNet(**kwargs)
    
    if weights is not None:
        model.load_state_dict(weights.get_state_dict(progress=progress))
        
    return model